<template>
  <div ref="container" class="calendar" :style="{height:height}">
    <div class="calendar-inner" :style="rootStyle">
      <div v-for="index in 7" :key="'title-'+index" :class="getTitleClass(index - 1)"/>
      <div v-for="date in monthDays" :key="'date-'+date.getDate()"
           :class="[getDateClass(date),{active:date===activeDate,today:date.getTime()===todayTime}]" @click="selectDate(date)">
        <div :class="date | dateClass(iOSVersion)"></div>
        <slot :name="'date-'+date.getDate()"></slot>
      </div>
    </div>
  </div>
</template>

<script>
  /**
   * 日历控件，按周显示某月，并支持选择日期；除高度和字体大小自动计算外，其它样式都可通过css来覆盖
   * 开发背景，是开发融创HR移动端时，没有可高度定制化的日历控件，现成PC端的控件用在移动端太重了
   * 当然你也可以同时用在PC端，从而一套代码，多端运行
   * 使用灰色调，是为了百搭，建议通过背景色和css来按需定制自己想要的颜色
   * 参数：
   *  month: 表示某月的Date实例
   * 事件：
   *  selected: 某日期被选择时触发，参数为表示被选择日期的Date实例
   * 插槽：
   *  date-[1~31]，某一个日期单元格内要显示的内容
   *
   * 使用示例：
   * <Calendar :month="new Date()" @selected="(date)=>log(date)"/>
   *   <div slot="date-1" style="position: absolute;bottom: 2px;font-size: 18px;">xx的生日</div>
   * </Calendar>
   */
  "use strict";
  export default {
    name: "Calendar",
    props: {
      month: {type: Date, default: new Date()},
    },
    data() {
      return {
        activeDate: null,
        iOSVersion: function () {
          const match = /iPhone OS (\d+)/.exec(navigator.userAgent);
          if (match && match[1]) {
            return parseInt(match[1]);
          }
        }(),
        height: "100vw",
        rootStyle: {},
      };
    },
    mounted() {
      this.calcSize();
    },
    computed: {
      templateDate() {
        const m = this.month ? this.month : new Date();
        return new Date(m.getFullYear(), m.getMonth());
      },
      todayTime() {
        const m = this.templateDate;
        return new Date(m.getFullYear(), m.getMonth(), new Date().getDate()).getTime();
      },
      monthDays: function () {
        const templateDate = this.templateDate;
        const month = templateDate.getMonth();
        const days = [];
        for (let i = 1; i <= 31; i++) {
          const date = new Date(templateDate.getTime());
          date.setDate(i);
          //当月已结束
          if (month !== date.getMonth()) {
            break;
          }
          days.push(date);
        }
        return days;
      },
    },
    filters: {
      dateClass: function (date, iOSVersion) {
        const classes = (iOSVersion && iOSVersion < 11) ? 'dateNo-ios10' : 'dateNo';
        return classes + ' day-of-month-' + date.getDate();
      }
    },
    methods: {
      calcSize() {
        const style = {};
        //动态调整高度，以适应每月不同周数
        style.height = this.calcHeight(1000) + "px";
        //缩放以适应容器
        const containerWidth = this.$refs.container.clientWidth;
        style['transform'] = `scale(${containerWidth / 1000})`;
        style['transform-origin'] = "0 0";
        //调整容器高度，以减少不必要的占位
        this.height = this.calcHeight(containerWidth) + "px";
        this.rootStyle = style;
      },
      calcHeight(width) {
        const templateDate = this.templateDate;
        const month = templateDate.getMonth();
        // 从31号往前找到当前月最后一天
        const date = new Date(templateDate.getTime());
        for (let i = 31; i > 0; i--) {
          date.setDate(i);
          //找到最后一天
          if (month === date.getMonth()) {
            const weekDay = date.getDay();
            const monthDay = date.getDate();
            // 计算当月一共跨多少周，从而计算高度
            const weekCount = (Math.ceil((monthDay - weekDay - 1) / 7) + 2)
            return weekCount * 14.3 / 100 * width;
          }
        }
      },
      getTitleClass(index) {
        const classes = [];
        classes.push('title');
        classes.push('day-of-week-' + index);
        return classes.join(' ');
      },
      getDateClass(date) {
        const classes = [];
        classes.push('date');
        const weekDay = date.getDay();
        const monthDay = date.getDate();
        const weekOfMonth = Math.ceil((monthDay - weekDay - 1) / 7);
        classes.push('day-of-week-' + weekDay);
        classes.push('week-of-month-' + (weekOfMonth + 1));
        return classes.join(' ');
      },
      /**
       * 日期被选中
       * @param date 被选中的日期
       */
      selectDate(date) {
        this.activeDate = date;
        this.$emit("selected", date);
      }
    }
  }
</script>

<style lang="scss" type="text/scss" scoped>
  .calendar {
    background-color: #f2f2f2;

    .calendar-inner {
      $innerUnit: "px";
      $weekdays: "日", "一", "二", "三", "四", "五", "六";
      $padding: 1;
      $gridSize: 138;
      $gap: 4;
      $font-size: 50;
      $offset: $gridSize + $gap;

      position: relative;
      box-sizing: border-box;
      margin: 0;
      border-width: 0;
      width: #{1000}#{$innerUnit};
      height: #{1000}#{$innerUnit};
      font-size: #{$font-size}#{$innerUnit};

      .date, .title {
        width: #{$gridSize}#{$innerUnit};
        height: #{$gridSize}#{$innerUnit};
        position: absolute;
        text-align: center;
      }

      .title {
        line-height: #{$gridSize}#{$innerUnit};
        background-color: #e9e9e9;
        text-align: center;
        top: #{$padding}#{'%'};
        color: #555;
      }

      .date {
        background-color: white;
        display: flex;
        vertical-align: middle;
        align-items: center;
        justify-content: center;
        cursor: pointer;
      }

      .dateNo {
        position: absolute;
        /*内容垂直居中*/
        display: flex;
        vertical-align: middle;
        align-items: center;
        justify-content: center;
        /*边框变化动画开始状态*/
        width: 50%;
        height: 50%;
        border-radius: 50%;

        &-ios10 {
          width: 100%;
          height: 100%;
          border-radius: 50%;
          text-align: center;
          border-width: #{35}#{$innerUnit};
          line-height: #{($gridSize - 35 * 2)}#{$innerUnit};
          border-style: solid;
          border-color: white;
        }
      }

      //当前日期的样式
      .today .dateNo {
        color: turquoise;
      }

      //选中日期样式
      .active .dateNo {
        /*边框变化动画*/
        transition: all .3s ease-out;
        width: 70%;
        height: 70%;
        background-color: turquoise;
        color: white;
        opacity: .8;

        &-ios10 {
          transition: all .3s ease-out;
          background-color: turquoise;
          border-width: #{22}#{$innerUnit};
          line-height: #{($gridSize - 22 * 2)}#{$innerUnit};
        }
      }

      //生成列样式，每列偏移量不同
      @for $i from 0 through 6 {
        .day-of-week-#{$i} {
          left: #{($offset * $i + $padding + $gap)}#{$innerUnit};

          &.title:before {
            content: nth($weekdays, $i+1);
          }
        }
      }

      //生成行样式，每行偏移量不同
      @for $i from 1 through 6 {
        .week-of-month-#{$i} {
          top: #{($offset * $i + $padding + $gap)}#{$innerUnit};
        }
      }

      //生成日期数字
      @for $i from 1 through 31 {
        .day-of-month-#{$i}:before {
          content: "#{$i}";
        }
      }
    }
  }

  @mixin calendar($unit) {
    .calendar {
      width: #{100}#{$unit};
      height: #{100}#{$unit};
    }
  }

  /*竖屏 css*/
  @media screen and (orientation: portrait) {
    @include calendar(vw);
  }

  /*横屏 css*/
  @media screen and (orientation: landscape) and (min-width: 450px) {
    @include calendar(vh);
  }
</style>